﻿using System.Collections.Generic;
using System.Linq;

using Outcoder.AI.NeuralNetworking;
using Outcoder.Collections;

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace Outcoder.Tests.AI.NeuralNetworking
{
	[TestClass]
	public class NeuralNetworkTimeSeries
	{
		/// <summary>
		///Gets or sets the test context which provides
		///information about and functionality for the current test run.
		///</summary>
		public TestContext TestContext { get; set; }

		[TestMethod]
		public void NeuralNetworkShouldFavourNoneWithoutTraining()
		{
//			var target = new NeuralNetwork(10, 10, 10);
////			var input = new[] { new [] {.1, .1, .1, .1, .1, .1, .1, .1, .1, .1} };
////			var output = new[] { new [] {.1, .1, .1, .1, .1, .1, .1, .1, .1, .1} };
////			target.Train(input, output, 5);
//
//			target.Input = new[] { .1, .1, .1, .1, .1, .1, .1, .1, .1, .1 };
//			target.Pulse();
//			
//			var indexOfGreatest = target.Output.GetIndexOfGreatest();
//			var error = target.GetOutputError(indexOfGreatest);
//			var greatestValue = target.Output.ToList()[indexOfGreatest];
//			Assert.IsTrue(greatestValue < .7);
			
		}

		[TestMethod]
		public void NeuralNetworkShouldPerformTimeSeries()
		{
			var input = new double[10][];
			var output = new double[10][];

			input[0] = new []  { .1, .1, .1, .1, .1, .1, .1, .1, .1, .1 };
			output[0] = new [] { .1, .1, .1, .1, .1, .1, .1, .1, .1, .1 };

			input[1] = new []  { .9, .1, .1, .1, .1, .1, .1, .1, .1, .1 };
			output[1] = new [] { .1, .9, .1, .1, .1, .1, .1, .1, .1, .1 };

			input[2] = new []  { .8, .9, .1, .1, .1, .1, .1, .1, .1, .1 };
			output[2] = new [] { .1, .1, .9, .1, .1, .1, .1, .1, .1, .1 };

			input[3] = new []  { .7, .8, .9, .1, .1, .1, .1, .1, .1, .1 };
			output[3] = new [] { .1, .1, .1, .9, .1, .1, .1, .1, .1, .1 };

			input[4] = new []  { .6, .7, .8, .9, .1, .1, .1, .1, .1, .1 };
			output[4] = new [] { .1, .1, .1, .1, .9, .1, .1, .1, .1, .1 };

			input[5] = new []  { .5, .6, .7, .8, .9, .1, .1, .1, .1, .1 };
			output[5] = new [] { .1, .1, .1, .1, .1, .9, .1, .1, .1, .1 };

			input[6] = new []  { .4, .5, .6, .7, .8, .9, .1, .1, .1, .1 };
			output[6] = new [] { .1, .1, .1, .1, .1, .1, .9, .1, .1, .1 };

			input[7] = new[]  { .3, .4, .5, .6, .7, .8, .9, .1, .1, .1 };
			output[7] = new[] { .1, .1, .1, .1, .1, .1, .1, .9, .1, .1 };

			input[8] = new[]  { .2, .3, .4, .5, .6, .7, .8, .9, .1, .1 };
			output[8] = new[] { .1, .1, .1, .1, .1, .1, .1, .1, .9, .1 };

			input[9] = new[]  { .1, .2, .3, .4, .5, .6, .7, .8, .9, .1 };
			output[9] = new[] { .1, .1, .1, .1, .1, .1, .1, .1, .1, .9 };

			var target = new NeuralNetwork(10, 10, 10);

			double soughtAccuracy = .75;
			var inputStimuli = from doubles in input
			                   select new LayerStimulus(doubles);
			var outputStimuli = from doubles in output
			                    select new LayerStimulus(doubles);
			var trainingSet = new TrainingSet(inputStimuli.ToList(), outputStimuli.ToList());
			var timedTrainingResult = target.Train(trainingSet, soughtAccuracy, 10000);
			string message = string.Format("Accuracy obtained should be greater than {0}, but was only {1}.",
										   soughtAccuracy, timedTrainingResult.AccuracyAttained);
			Assert.AreEqual(TrainingResult.Success, timedTrainingResult.TrainingResult, message);
			Assert.IsTrue(timedTrainingResult.AccuracyAttained > soughtAccuracy, message);

			int actualIndex;
			for (int i = 1; i < input.Length; i++)
			{
				target.Input = input[i];
				target.Pulse();
				actualIndex = GetIndexOfHighest(target.Output);
				Assert.AreEqual(i, actualIndex);
			}

			var liveInput = new[] { .1, .1, .1, .7, .8, .9, .1, .1, .1, .1 };
			target.Input = liveInput;
			target.Pulse();
			actualIndex = target.Output.GetIndexOfGreatest();//GetIndexOfHighest(target.Output);
			Assert.AreEqual(6, actualIndex);
		}

		public static int GetIndexOfHighest(IEnumerable<double> output)
		{
			int result = -1;
			int count = 0;
			double highest = -1;
			foreach (var d in output)
			{
				if (d > highest)
				{
					highest = d;
					result = count;
				}
				count++;
			}
			return result;
		}
	}
}
